#region Using Directives

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Collections.Specialized;
using System.Collections;
using System.Web.Security;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.ComponentModel;

#endregion

namespace Smugmug
{
    public class QueryStringHelper : ICloneable, IEnumerable<KeyValuePair<string, string>>
    {
        #region Fields

        private static readonly string CheckSumKey = "CHK";
        private string start = "?";
        private string pairSeparator = "&";
        private string valueSeparator = "=";
        private bool includeCheckSum = false;
        //Dictionary<string, string> values = new Dictionary<string, string>();
        private NameValueCollection values = new NameValueCollection();

        #endregion

        #region Constructors

        public QueryStringHelper()
        {
        }

        public QueryStringHelper(string queryString)
        {
            if (String.IsNullOrEmpty(queryString))
                return;

            values = HttpUtility.ParseQueryString(queryString);
        }

        public QueryStringHelper(Uri uri)
            : this(uri.Query)
        {
        }

        public QueryStringHelper(QueryStringHelper source)
        {
            if (source == null)
                return;
            IncludeCheckSum = source.IncludeCheckSum;
            foreach (KeyValuePair<string, string> item in source)
            {
                Add(HttpUtility.UrlDecode(item.Key), HttpUtility.UrlDecode(item.Value));
            }
            Debug.Assert(source.Count == Count);
        }

        public QueryStringHelper(NameValueCollection queryString)
        {
            if (queryString == null)
                return;
            foreach (string q in queryString)
            {
                if (q != null && q != String.Empty)
                    Add(HttpUtility.UrlDecode(q), HttpUtility.UrlDecode(queryString[q]));
            }
            Debug.Assert(queryString.Count == Count);
        }

        #endregion

        #region Properties

        public ICollection<string> Keys
        {
            get { return values.AllKeys; }
        }

        public ICollection<string> Values
        {
            get
            {
                List<string> l = new List<string>();
                foreach (string key in values.AllKeys)
                {
                    l.Add(values[key]);
                }
                return l;
            }
        }

        public bool IncludeCheckSum
        {
            get { return includeCheckSum; }
            set { includeCheckSum = value; }
        }

        public int Count
        {
            get { return values.Count; }
        }

        public string this[string name]
        {
            get { return Get(name); }
            set { Add(name, value); }
        }

        public static QueryStringHelper Current
        {
            get
            {
                if (HttpContext.Current == null)
                    return new QueryStringHelper();
                return new QueryStringHelper(HttpContext.Current.Request.QueryString);
            }
        }

        #endregion

        #region Methods

        public string Get(string name)
        {
            if (String.IsNullOrEmpty(name) || !ContainsKeyInternal(name))
                return String.Empty;
            Debug.Assert(values[name] != null);
            return values[name];
        }

        public void Add(string name, string value)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            if (String.IsNullOrEmpty(value))
                values[name] = String.Empty;
            else
                values[name] = value;
        }

        public void Remove(string name)
        {
            if (String.IsNullOrEmpty(name) || !ContainsKeyInternal(name))
                return;
            values.Remove(name);
        }

        public bool ContainsKey(string name)
        {
            if (String.IsNullOrEmpty(name))
                return false;
            return ContainsKeyInternal(name);
        }

        private bool ContainsKeyInternal(string name)
        {
            foreach (string key in values.AllKeys)
            {
                if (key.Equals(name, StringComparison.CurrentCultureIgnoreCase))
                    return true;
            }
            return false;
        }

        public bool ContainsValue(string value)
        {
            if (String.IsNullOrEmpty(value))
                return ContainsValueInternal(String.Empty);
            return ContainsValueInternal(value);
        }

        private bool ContainsValueInternal(string value)
        {
            foreach (string key in values.AllKeys)
            {
                if (values[key].Equals(value, StringComparison.CurrentCultureIgnoreCase))
                    return true;
            }
            return false;
        }

        public void AddRange(IEnumerable<KeyValuePair<string, string>> query)
        {
            if (query == null)
                return;

            foreach (KeyValuePair<string, string> pair in query)
            {
                values[pair.Key] = pair.Value;
            }
        }

        public void RemoveRange(QueryStringHelper data)
        {
            if (data == null)
                return;
            RemoveRange(data.Keys);
        }

        public void RemoveRange(ICollection<string> names)
        {
            if (names == null)
                return;
            foreach (string name in names)
            {
                Remove(name);
            }
        }

        public static string GetValue(string name)
        {
            if (String.IsNullOrEmpty(name))
                return String.Empty;
            QueryStringHelper qs = QueryStringHelper.Current;
            if (qs == null)
                return String.Empty;
            return qs[name];
        }

        #endregion

        #region QueryString Check Sum Methods

        /// <summary>
        /// Modifies the query string to have a checksum used to prevent tampering.
        /// </summary>
        /// <param name="query"></param>
        private void AddCheckSum()
        {
            values[CheckSumKey] = GetCheckSumString(this);
        }

        public static string GetCheckSumString(QueryStringHelper query)
        {
            StringBuilder s = new StringBuilder();
            foreach (KeyValuePair<string, string> items in query)
            {
                if (items.Key != CheckSumKey)
                {
                    s.Append(items.Key);
                    s.Append(items.Value);
                }
            }

            if (s.Length == 0)
                return String.Empty;
            return FormsAuthentication.HashPasswordForStoringInConfigFile(s.ToString(), "sha1");
        }

        /// <summary>
        /// Checks the current query string for tampering
        /// </summary>
        public bool IsTampered
        {
            get { EnsureCheckSumAdded(); return CheckTampering(this); }
        }

        private void EnsureCheckSumAdded()
        {
            if (IncludeCheckSum && !ContainsKey(CheckSumKey))
                AddCheckSum();
        }

        /// <summary>
        /// Checks the given query string for tampering
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public static bool CheckTampering(QueryStringHelper query)
        {
            return query[CheckSumKey] != GetCheckSumString(query);
        }

        #endregion

        #region Retrieving Values

        #region GetInt32

        public int? GetInt32(string name)
        {
            return GetInt32(name, null);
        }

        public int? GetInt32(string name, int? defaultValue)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            string val = this[name];
            if (String.IsNullOrEmpty(val))
            {
                return defaultValue;
            }
            else
            {
                int result;
                if (Int32.TryParse(val, out result))
                {
                    return new Nullable<int>(result);
                }
                return defaultValue;
            }
        }

        #endregion

        #region GetDouble

        public double? GetDouble(string name)
        {
            return GetDouble(name, null);
        }

        public double? GetDouble(string name, double? defaultValue)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            string val = this[name];
            if (String.IsNullOrEmpty(val))
            {
                return defaultValue;
            }
            else
            {
                double result;
                if (Double.TryParse(val, out result))
                {
                    return new Nullable<double>(result);
                }
                return defaultValue;
            }
        }

        #endregion

        #region GetBoolean

        public bool? GetBoolean(string name)
        {
            return GetBoolean(name, null);
        }

        public bool? GetBoolean(string name, bool? defaultValue)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            string val = this[name];
            if (String.IsNullOrEmpty(val))
            {
                return defaultValue;
            }
            else
            {
                bool result;
                if (Boolean.TryParse(val, out result))
                {
                    return new Nullable<bool>(result);
                }
                return defaultValue;
            }
        }

        #endregion

        #region GetDateTime

        public DateTime? GetDateTime(string name)
        {
            return GetDateTime(name, null);
        }

        public DateTime? GetDateTime(string name, DateTime? defaultValue)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            string val = this[name];
            if (String.IsNullOrEmpty(val))
            {
                return defaultValue;
            }
            else
            {
                DateTime result;
                if (DateTime.TryParse(val, out result))
                {
                    return new Nullable<DateTime>(result);
                }
                return defaultValue;
            }
        }

        #endregion

        #endregion

        #region ToString()

        public override string ToString()
        {
            if (values.Count < 1)
                return String.Empty;

            if (IncludeCheckSum)
                AddCheckSum();

            DelimitedList<string> items = new DelimitedList<string>(pairSeparator);
            foreach (string key in values.AllKeys)
            {
                items.Add(String.Format("{0}{1}{2}", HttpUtility.UrlEncode(key), valueSeparator, HttpUtility.UrlEncode(values[key])));
            }

            return String.Format("{0}{1}", start, items);
        }

        public override int GetHashCode()
        {
            return Keys.GetHashCode() + Values.GetHashCode();
        }

        #endregion

        #region ICloneable Members

        object ICloneable.Clone()
        {
            return Clone();
        }

        public QueryStringHelper Clone()
        {
            return new QueryStringHelper(this);
        }

        #endregion

        #region IEnumerable<KeyValuePair<string,string>> Members

        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
        {
            foreach (string key in values.AllKeys)
            {
                yield return new KeyValuePair<string, string>(key, values[key]);
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }

        #endregion
    }
}